/*
JNG image loading library v1.0.1
(c) 2012 Aleksander Similyetov (elmm00@gmail.com)
*/

#include "jng_load.h"

#include <memory.h>
#include <stdio.h>

#include "jpeglib.h"
#include "zlib.h"

/*#define JNG_TRACK_MEM*/

#ifdef JNG_TRACK_MEM
#define JNG_MAX_ALLOC_TRACK_COUNT	512
#endif

#if defined(JNG_ZLIB_BUF_SIZE) && (JNG_ZLIB_BUF_SIZE < 1024)
#	undef JNG_ZLIB_BUF_SIZE
#endif

#ifndef JNG_ZLIB_BUF_SIZE
#	define JNG_ZLIB_BUF_SIZE 8192
#endif


#define JNG_TRACE_ERROR_CHUNK(jngHandle, errorCode, chunkId, chunkSeq)	((jngHandle)->errorProc(jngHandle, errorCode, chunkId, chunkSeq, "chunk error"))
#define JNG_TRACE_ERROR_COMMON(jngHandle, errorCode) ((jngHandle)->errorProc(jngHandle, errorCode, 0, -1, "error"))
#define JNG_TRACE_ERROR_ARG(jngHandle) ((jngHandle)->errorProc(jngHandle, JNG_ERR_INVALID_FUNCTION_ARGUMENT, 0, -1, "invalid args"))
#define JNG_TRACE_ERROR_MALLOC(jngHandle) ((jngHandle)->errorProc(jngHandle, JNG_ERR_NOT_ENOUGH_MEM, 0, -1, "not enough mem"))

#define JNG_ASSERT_ALWAYS(param)

/*Network byte order to machine byte order macro:*/

#ifdef JNG_BIG_ENDIAN

#	define JNG_NBO2MBO_32U(val) (val)
#	define JNG_NBO2MBO_32S(val) (val)

#	define JNG_NBO2MBO_16U(val) (val)
#	define JNG_NBO2MBO_16S(val) (val)


#else

#	define JNG_NBO2MBO_32U(val) ( ((((unsigned long)(val)) & 0xff) << 24) | ((((unsigned long)(val)) & 0xff00) << 8) | ((((unsigned long)(val)) & 0xff0000) >> 8) | ((((unsigned long)(val)) & 0xff000000) >> 24) )
#	define JNG_NBO2MBO_32S(val) ( (signed long)(((((unsigned long)(val)) & 0xff) << 24) | ((((unsigned long)(val)) & 0xff00) << 8) | ((((unsigned long)(val)) & 0xff0000) >> 8) | ((((unsigned long)(val)) & 0xff000000) >> 24)) )

#	define JNG_NBO2MBO_16U(val) ( ((((unsigned short)(val)) >> 8) & 0xff) | ((((unsigned short)(val)) << 8) & 0xff00) )
#	define JNG_NBO2MBO_16S(val) ( ((((signed short)(val)) >> 8) & 0xff) | ((((signed short)(val)) << 8) & 0xff00) )

#endif/*JNG_BIG_ENDIAN*/

/*PNG defines*/
#define PNG_FILTER_VALUE_NONE  0
#define PNG_FILTER_VALUE_SUB   1
#define PNG_FILTER_VALUE_UP    2
#define PNG_FILTER_VALUE_AVG   3
#define PNG_FILTER_VALUE_PAETH 4

#ifndef PNG_ROWBYTES
#define PNG_ROWBYTES(pixel_bits, width) \
	((pixel_bits) >= 8 ? \
	((width) * (((unsigned long)(pixel_bits)) >> 3)) : \
	(( ((width) * ((unsigned long)(pixel_bits))) + 7) >> 3) )
#endif/*PNG_ROWBYTES*/

/*alpha compression format*/
enum
{
	JNG_ALPHA_COMPRESSION_PNG = 0,
	JNG_ALPHA_COMPRESSION_JPEG = 8
};

/*known chunk's names*/
#define CHUNK_ID_IDAT JNG_NBO2MBO_32U('IDAT')
#define CHUNK_ID_IEND JNG_NBO2MBO_32U('IEND')
#define CHUNK_ID_JDAA JNG_NBO2MBO_32U('JDAA')
#define CHUNK_ID_JDAT JNG_NBO2MBO_32U('JDAT')
#define CHUNK_ID_JHDR JNG_NBO2MBO_32U('JHDR')
#define CHUNK_ID_JSEP JNG_NBO2MBO_32U('JSEP')

/*JNG signature*/
static const unsigned char jngSig[] =  {0x8b, 0x4a, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
/*precalculated table for fast CRC*/
static const unsigned long crcTable[256] = {
0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D,
};

typedef enum 
{
	JNG_PIXEL_SKIP = -1,
	JNG_PIXEL_COPY = 0,
	JNG_PIXEL_UPSCALE = 1,
	JNG_PIXEL_DOWNSCALE = 2
} JNG_PIXEL_PROCESS_MODE;


/************************************************************************/

#pragma pack(push)
#pragma pack(1)

typedef struct _jhdr
{
	unsigned long	width;
	unsigned long	height;
	unsigned char	colorType;
	unsigned char	imageSampleDepth;
	unsigned char	imageCompression;
	unsigned char	imageInterlace;
	unsigned char	alphaSampleDepth;
	unsigned char	alphaCompression;
	unsigned char	alphaFilter;
	unsigned char	alphaInterlace;
} jhdr, *pjhdr;

/************************************************************************/

typedef struct _chunk_descriptor
{
	/*pointer to next descriptor*/
	void*			nextDescriptor;
	jng_bool		ownChunkData;
	int				chunkSeq;
	/*actual chunk data*/
	pjng_chunk chunkPtr;
		
} chunk_descriptor, *pchunk_descriptor;

#pragma pack(pop)

/************************************************************************/
struct _jng_decoder_interface;
typedef struct _jng_decoder_interface * pjng_decoder_interface;

typedef jng_bool (*pjng_decode_read_scanline)(pjng_decoder_interface decoder, void* buffer);
typedef jng_bool (*pjng_decode_finish)(pjng_decoder_interface decoder);

typedef struct _jng_decoder_interface
{
	jng_handle					handle;
	pjng_decode_read_scanline	readLineProc;
	pjng_decode_finish			finishProc;
	int							bytesPerChannel;
	pchunk_descriptor			chunkDataDesciptor;
	size_t						dataOffsetInDescriptor;
} jng_decoder_interface;

typedef struct _jng_decode_struct
{
	void*	alphaScanline;
	void*	colorScanline;
	pjng_decoder_interface	alphaDecoder;
	pjng_decoder_interface	colorDecoder;
	jng_bool				errorState;
	size_t					currentDecodeLine;
} jng_decode_struct, *pjng_decode_struct;

/************************************************************************/
/* JPEG decoder definitions*/

struct _jng_decoder_jepg;
typedef struct _jng_decoder_jepg * pjng_decoder_jepg;

typedef struct _jng_decoder_jepg_source_mgr
{
	struct jpeg_source_mgr	pub;			//public fields
	pjng_decoder_jepg		decoderPtr;
} jng_decoder_jepg_source_mgr, *pjng_decoder_jepg_source_mgr;

typedef struct _jng_decoder_jepg
{
	jng_decoder_interface			decoderCommon;/*public interface*/
	struct jpeg_decompress_struct	cinfo;
	struct jpeg_error_mgr			jerr;
	jng_decoder_jepg_source_mgr		src;
	JSAMPROW						scanlines[1];
} jng_decoder_jepg;

/************************************************************************/
/* PNG decoder definitions*/
typedef struct _jng_decoder_png
{
	jng_decoder_interface	decoderCommon;/*public interface*/
	z_stream				zStream;
	unsigned long			rowPixelBytesNum;
	unsigned long			rowBufferBytesNum;
	unsigned char*			rowBytesCur;
	unsigned char*			rowBytesPrev;
} jng_decoder_png, *pjng_decoder_png;

/************************************************************************/

#ifdef JNG_TRACK_MEM
typedef struct _jng_alloc_info
{
	const char* file;
	int			line;
	size_t		size;
} jng_alloc_info, *pjng_alloc_info;
#endif

/************************************************************************/

typedef struct _jng_info
{
	/*base fields*/
	jng_alloc		allocProc;
	jng_free		freeProc;
	jng_errorproc	errorProc;
	void*			userData;
	unsigned long	flags;
	/*data input fields*/
	const unsigned char*	data;
	size_t					dataSize;
	size_t					dataOffset;
	/*or stream input fields*/
	jng_readproc	readProc;
	jng_skipproc	skipProc;
	/*read stuff*/
	int	nextChunkSeq;
	pchunk_descriptor	chunksHead;
	pchunk_descriptor	chunksTail;
	jng_bool	readSucceeded;
	/*sorted chunk references*/
	pchunk_descriptor	chunkRefJhdr;
	pchunk_descriptor	chunksRefAlphaHead;
	pchunk_descriptor	chunksRefAlphaTail;
	pchunk_descriptor	chunksRefJdat8Head;
	pchunk_descriptor	chunksRefJdat8Tail;
	pchunk_descriptor	chunksRefJdat12Head;
	pchunk_descriptor	chunksRefJdat12Tail;
	/*decode options*/
	int					alphaOutSampleBitsDepth;
	int					colorOutSampleBitsDepth;
	JNG_COLOR_SPACE		outColorSpace;
	double				outputGamma;
	JNG_JPEG_SRC_TYPE	jpegSourceType;
	jng_bool			outAlphaBeforeColor;
	/*decoder's stuff*/
	pjng_decode_struct	decodeStruct;
	/*debug stuff*/
#ifdef JNG_TRACK_MEM
	size_t	allocTotal;
	size_t	allocCallCount;
	size_t	freeCallCount;
	size_t	maxAllocBlock;
	int		nextAllocId;
	jng_alloc_info	allocs[JNG_MAX_ALLOC_TRACK_COUNT];
#endif
} jng_info, *pjng_info;



/************************************************************************/
/*forward declarations                                                  */
/*all functions whose names start with 'ijng_' are for internal use only*/

#ifdef JNG_TRACK_MEM
#	define ijng_alloc(h, a) ijng_alloc4(h, a, __FILE__, __LINE__)
#else
#	define ijng_alloc(h, a) ijng_alloc2(h, a)
#endif

void* ijng_alloc2(jng_handle handle, size_t size);
void* ijng_alloc4(jng_handle handle, size_t size, const char* fileName, int line);
void ijng_free(jng_handle handle, void* ptr);

pchunk_descriptor ijng_alloc_descriptor(jng_handle handle);
void ijng_free_descriptor(jng_handle handle, pchunk_descriptor descriptor);

jng_handle ijng_create_common(void* userData, jng_alloc allocPtr, jng_free freePtr, jng_errorproc errorPtr, unsigned long flags);
jng_bool ijng_init_common(jng_handle handle);

jng_bool ijng_input_read(jng_handle handle, void* buffer, size_t size);
jng_bool ijng_input_skip(jng_handle handle, size_t size);
const void* ijng_data_get_current_ptr(jng_handle handle);

pchunk_descriptor ijng_free_chunk(jng_handle handle, pchunk_descriptor chunkDescriptor);/*returns pointer from nextDescriptor feild of  chunkDescriptor*/
void ijng_free_chunks_list(jng_handle handle, pchunk_descriptor headDescriptor);
jng_bool ijng_check_chunk_crc(jng_handle handle, pchunk_descriptor chunkDescriptor);
pchunk_descriptor ijng_read_next_chunk(jng_handle handle);
pchunk_descriptor ijng_create_chunk_ref(jng_handle handle, pchunk_descriptor chunk);
void ijng_chunk_list_add(jng_handle handle, pchunk_descriptor chunkDescriptor, pchunk_descriptor* head, pchunk_descriptor* tail);

#define ijng_jhdr(hh) ((pjhdr)((hh)->chunkRefJhdr->chunkPtr->data))

pjng_decoder_interface ijng_decode_create_alpha_png(jng_handle handle, pchunk_descriptor chunksRefHead, int outBytesPerChannel);
pjng_decoder_interface ijng_decode_create_alpha_jpeg(jng_handle handle, pchunk_descriptor chunksRefHead, int outBytesPerChannel);

pjng_decoder_interface ijng_decode_create_color_jpeg(jng_handle handle, pchunk_descriptor chunksRefHead, int outBytesPerChannel, JNG_JPEG_SRC_TYPE bitsPerChannel);

/*JPEG's forward declarations*/

jng_bool ijng_decode_read_scanline_jpeg(pjng_decoder_interface decoder, void* buffer);
jng_bool ijng_decode_finish_jpeg(pjng_decoder_interface decoder);

/*PNG's forward declarations*/

voidpf jng_zlib_alloc(voidpf opaque, uInt items, uInt size);
void   jng_zlib_free(voidpf opaque, voidpf address);

jng_bool ijng_decode_read_scanline_png(pjng_decoder_interface decoder, void* buffer);
jng_bool ijng_decode_finish_png(pjng_decoder_interface decoder);
jng_bool ijng_decode_read_filter_row_png(pjng_decoder_interface decoder, size_t byteInRow, int filterType, unsigned char* currentRow, unsigned char* prevRow);

/************************************************************************/

#ifdef JNG_TRACK_MEM

void* ijng_alloc4(jng_handle handle, size_t size, const char* fileName, int line)
{
	int* allocRes;

	handle->allocTotal += size;
	if(size > handle->maxAllocBlock)
		handle->maxAllocBlock = size;
	handle->allocCallCount++;

	allocRes = (int*)handle->allocProc(size + sizeof(int));
	handle->allocs[handle->nextAllocId].file = fileName;
	handle->allocs[handle->nextAllocId].line = line;
	handle->allocs[handle->nextAllocId].size = size;
	*allocRes = handle->nextAllocId++;
	
	return (void*)(allocRes + 1);
}

void ijng_free(jng_handle handle, void* ptr)
{
	int* realPtr = (int*)(((unsigned char*)ptr) - sizeof(int));
#ifdef JNG_TRACK_MEM
	handle->freeCallCount++;
#endif

	memset(&handle->allocs[*realPtr], 0, sizeof(jng_alloc_info));

	handle->freeProc(realPtr);
}

#else

void* ijng_alloc2(jng_handle handle, size_t size)
{
	return handle->allocProc(size);
}

void ijng_free(jng_handle handle, void* ptr)
{
	handle->freeProc(ptr);
}

#endif/*JNG_TRACK_MEM*/

pchunk_descriptor ijng_alloc_descriptor(jng_handle handle)
{
	pchunk_descriptor descriptor = (pchunk_descriptor)ijng_alloc(handle, sizeof(chunk_descriptor));
	memset(descriptor, 0, sizeof(chunk_descriptor));

	return descriptor;
}

void ijng_free_descriptor(jng_handle handle, pchunk_descriptor descriptor)
{
	ijng_free(handle, descriptor);
}

jng_handle ijng_create_common(void* userData, jng_alloc allocPtr, jng_free freePtr, jng_errorproc errorPtr, unsigned long flags)
{
	pjng_info jngInfo;

	if(allocPtr == NULL || freePtr == NULL || errorPtr == NULL)
		return NULL;

	jngInfo = (pjng_info)allocPtr(sizeof(jng_info));
	if(!jngInfo)
		return NULL;

	memset(jngInfo, 0, sizeof(jng_info));

	jngInfo->allocProc = allocPtr;
	jngInfo->freeProc = freePtr;
	jngInfo->errorProc = errorPtr;
	jngInfo->userData = userData;
	jngInfo->flags = flags;

	return jngInfo;
}

jng_bool ijng_init_common(jng_handle handle)
{
	unsigned char inputSig[sizeof(jngSig)];

	JNG_ASSERT_ALWAYS(handle);
	
	/*check signature*/
	if(!ijng_input_read(handle, inputSig, sizeof(jngSig)))
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_READ_SIGNATRUE);

		return JNG_FALSE;
	}

	if(memcmp(jngSig, inputSig, sizeof(jngSig)))
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_INCORRECT_SIGNATRUE);

		return JNG_FALSE;
	}

	handle->outColorSpace = JNG_RGB;
	handle->outputGamma = 1.0;

	return JNG_TRUE;
}

jng_handle jng_create_from_data(void* userData, const void* data, size_t size, jng_alloc allocPtr, jng_free freePtr, jng_errorproc errorPtr, unsigned long flags)
{
	jng_handle handle;

	/*check input args*/
	if(data == NULL || size <= sizeof(jngSig) + sizeof(jhdr) + sizeof(jng_chunk_header) + 2)
	{
		return NULL;
	}
	
	handle = ijng_create_common(userData, allocPtr, freePtr, errorPtr, flags);
	if(!handle)
		return NULL;

	handle->data = (const unsigned char*)data;
	handle->dataSize = size;

	if(!ijng_init_common(handle))
	{
		jng_cleanup(handle);
		return NULL;
	}

	return handle;
}

jng_handle jng_create_from_stream(void* userData, jng_readproc readPtr, jng_skipproc skipPtr, jng_alloc allocPtr, jng_free freePtr, jng_errorproc errorPtr, unsigned long flags)
{
	jng_handle handle;

	/*check input args*/
	if(readPtr == NULL || skipPtr == NULL)
	{
		return NULL;
	}
	
	handle = ijng_create_common(userData, allocPtr, freePtr, errorPtr, flags);
	if(!handle)
		return NULL;

	handle->readProc = readPtr;

	if(!ijng_init_common(handle))
	{
		jng_cleanup(handle);
		return NULL;
	}

	return handle;
}

void jng_cleanup(jng_handle handle)
{
	pchunk_descriptor	currentDescriptor = NULL;
	pchunk_descriptor	nextDescriptor = NULL;
	jng_free			freePtr = NULL;

	if(!handle)
		return;

	if(handle->decodeStruct)
		jng_finish_decode(handle);

	ijng_free_chunks_list(handle, handle->chunksHead);
	
	ijng_free_chunks_list(handle, handle->chunkRefJhdr);
	ijng_free_chunks_list(handle, handle->chunksRefAlphaHead);
	ijng_free_chunks_list(handle, handle->chunksRefJdat8Head);
	ijng_free_chunks_list(handle, handle->chunksRefJdat12Head);

	freePtr = handle->freeProc;
#ifdef JNG_TRACK_MEM
	JNG_ASSERT_ALWAYS(handle->allocCallCount == handle->freeCallCount);
#endif
	memset(handle, 0, sizeof(jng_handle));/*zero mem, to mimimize errors in case some one will try to access deallocated memmory*/
	freePtr(handle);
}

void* jng_get_user_data(jng_handle handle)
{
	if(!handle)
		return NULL;

	return handle->userData;
}

jng_bool ijng_input_read(jng_handle handle, void* buffer, size_t size)
{
	size_t readResult = 0;

	JNG_ASSERT_ALWAYS(handle);

	if(size==0)
		return JNG_TRUE;

	if(handle->readProc != NULL)
	{
		readResult = handle->readProc(handle, buffer, size);
	}
	else
	{
		if(size < handle->dataSize - handle->dataOffset)
		{
			memcpy(buffer, handle->data + handle->dataOffset, size);
			handle->dataOffset += size;
			readResult = size;
		}
	}

	return readResult==size;
}

jng_bool ijng_input_skip(jng_handle handle, size_t size)
{
	size_t skipResult = 0;

	JNG_ASSERT_ALWAYS(handle);

	if(!size)
		return JNG_TRUE;

	if(handle->readProc != NULL)
	{
		skipResult = handle->skipProc(handle, size);
	}
	else
	{
		if(size <= handle->dataSize - handle->dataOffset)
		{
			handle->dataOffset += size;
			skipResult = size;
		}
	}

	return skipResult==size;
}

const void* ijng_data_get_current_ptr(jng_handle handle)
{
	unsigned const char* dataPtr;
	
	JNG_ASSERT_ALWAYS(handle);

	if(handle->dataOffset >= handle->dataSize)
		return NULL;

	dataPtr = ((unsigned const char*)handle->data) + handle->dataOffset;

	return (const void*)dataPtr;
}

pchunk_descriptor ijng_free_chunk(jng_handle handle, pchunk_descriptor chunkDescriptor)
{
	pchunk_descriptor nextDescriptor;

	JNG_ASSERT_ALWAYS(handle);

	if(chunkDescriptor == NULL)
		return NULL;

	nextDescriptor = (pchunk_descriptor)chunkDescriptor->nextDescriptor;

	if(chunkDescriptor->ownChunkData && chunkDescriptor->chunkPtr)
	{
		ijng_free(handle, (void*)chunkDescriptor->chunkPtr);
	}
	ijng_free_descriptor(handle, chunkDescriptor);

	return nextDescriptor;
}

void ijng_free_chunks_list(jng_handle handle, pchunk_descriptor headDescriptor)
{
	pchunk_descriptor	nextDescriptor = NULL;

	JNG_ASSERT_ALWAYS(handle);
	JNG_ASSERT_ALWAYS(headDescriptor);

	while(headDescriptor)
	{
		nextDescriptor = (pchunk_descriptor)headDescriptor->nextDescriptor;

		headDescriptor->nextDescriptor = NULL;
		ijng_free_chunk(handle, headDescriptor);

		headDescriptor = nextDescriptor;
	}
}

jng_bool ijng_check_chunk_crc(jng_handle handle, pchunk_descriptor chunkDescriptor)
{
	const unsigned char* data;
	size_t	sizeToCalc;
	unsigned long crc = 0xffffffff;
	unsigned long* chunkCrcPtr;
	size_t byteNum = 0;
	jng_bool isChunkCritical;

	JNG_ASSERT_ALWAYS(handle);
	JNG_ASSERT_ALWAYS(chunkDescriptor);

	data = (const unsigned char *)&(chunkDescriptor->chunkPtr->header.chunkType);
	sizeToCalc = JNG_NBO2MBO_32U(chunkDescriptor->chunkPtr->header.dataLength) + 4;/*size of header.chunkType*/

	for (byteNum = 0; byteNum < sizeToCalc; byteNum++)
		crc = crcTable[(crc ^ data[byteNum]) & 0xff] ^ (crc >> 8);

	crc = crc ^ 0xffffffffU;

	chunkCrcPtr = (unsigned long* )(chunkDescriptor->chunkPtr->data + JNG_NBO2MBO_32U(chunkDescriptor->chunkPtr->header.dataLength));
	if(crc==JNG_NBO2MBO_32U(*chunkCrcPtr))
	{
		return JNG_TRUE;
	}

	isChunkCritical = ((chunkDescriptor->chunkPtr->header.chunkType.charVal[0] & 0x20) == 0);
	if(isChunkCritical && (handle->flags &JNG_FLAG_CRC_CRITICAL))
	{
		/*chunk is critical*/
		return JNG_FALSE;
	}
	
	if(!isChunkCritical && (handle->flags & JNG_FLAG_CRC_ANCILLARY))
	{
		return JNG_FALSE;
	}
	
	return JNG_TRUE;
}

pchunk_descriptor ijng_read_next_chunk(jng_handle handle)
{
	pchunk_descriptor descriptor;
	size_t readResult;
	jng_chunk_header header;

	JNG_ASSERT_ALWAYS(handle);

	descriptor = ijng_alloc_descriptor(handle);
	
	if(!descriptor)
	{
		JNG_TRACE_ERROR_MALLOC(handle);
		return NULL;
	}

	if(handle->readProc)
	{
		readResult = handle->readProc(handle, &header, sizeof(jng_chunk_header));
		if(readResult != sizeof(jng_chunk_header))
		{
			JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_READ_CHUNK_HEADER);
			ijng_free_chunk(handle, descriptor);
			return NULL;
		}

		descriptor->chunkPtr = (pjng_chunk)ijng_alloc(handle, JNG_NBO2MBO_32U(header.dataLength) + sizeof(jng_chunk) + 4);/*4 - size of crc*/

		if(descriptor->chunkPtr==NULL)
		{
			JNG_TRACE_ERROR_MALLOC(handle);
			ijng_free_chunk(handle, descriptor);
			return NULL;
		}

		descriptor->ownChunkData = JNG_TRUE;
		descriptor->chunkPtr->header = header;
		readResult = handle->readProc(handle, (void*)&descriptor->chunkPtr->data, JNG_NBO2MBO_32U(header.dataLength) + 4);

		if(readResult != JNG_NBO2MBO_32U(header.dataLength) + 4)
		{
			JNG_TRACE_ERROR_CHUNK(handle, descriptor->chunkPtr->header.chunkType.longVal, handle->nextChunkSeq, JNG_ERR_READ_CHUNK_DATA);
			ijng_free_chunk(handle, descriptor);
			return NULL;
		}
	}
	else
	{
		descriptor->ownChunkData = JNG_FALSE;
		descriptor->chunkPtr = (pjng_chunk)ijng_data_get_current_ptr(handle);
		if(!ijng_input_skip(handle, sizeof(jng_chunk_header)))
		{
			JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_READ_CHUNK_HEADER);
			ijng_free_chunk(handle, descriptor);
			return NULL;
		}

		/*move data pointer accroding to data size*/
		if(!ijng_input_skip(handle, JNG_NBO2MBO_32U(descriptor->chunkPtr->header.dataLength)))
		{
			JNG_TRACE_ERROR_CHUNK(handle, descriptor->chunkPtr->header.chunkType.longVal, handle->nextChunkSeq, JNG_ERR_READ_CHUNK_DATA);
			ijng_free_chunk(handle, descriptor);
			return NULL;
		}

		/*move data pointer according to crc size */
		if(!ijng_input_skip(handle, 4))
		{
			JNG_TRACE_ERROR_CHUNK(handle, descriptor->chunkPtr->header.chunkType.longVal, handle->nextChunkSeq, JNG_ERR_READ_CHUNK_DATA);
			ijng_free_chunk(handle, descriptor);
			return NULL;
		}
	}

	descriptor->chunkSeq = handle->nextChunkSeq++;

	if(!ijng_check_chunk_crc(handle, descriptor))
	{
		JNG_TRACE_ERROR_CHUNK(handle, descriptor->chunkPtr->header.chunkType.longVal, descriptor->chunkSeq, JNG_ERR_CHUNK_CRC);
		ijng_free_chunk(handle, descriptor);
		return NULL;
	}

	return descriptor;
}

pchunk_descriptor ijng_create_chunk_ref(jng_handle handle, pchunk_descriptor chunk)
{
	pchunk_descriptor newDescriptor;

	JNG_ASSERT_ALWAYS(handle);
	JNG_ASSERT_ALWAYS(chunk);

	newDescriptor = ijng_alloc_descriptor(handle);
	
	if(!newDescriptor)
	{
		JNG_TRACE_ERROR_MALLOC(handle);
		return NULL;
	}

	newDescriptor->ownChunkData = JNG_FALSE;
	newDescriptor->chunkPtr = chunk->chunkPtr;
	newDescriptor->chunkSeq = chunk->chunkSeq;

	return newDescriptor;
}

void ijng_chunk_list_add(jng_handle handle, pchunk_descriptor chunkDescriptor, pchunk_descriptor* head, pchunk_descriptor* tail)
{
	JNG_ASSERT_ALWAYS(handle);
	JNG_ASSERT_ALWAYS(chunk);
	JNG_ASSERT_ALWAYS(head);
	JNG_ASSERT_ALWAYS(tail);

	chunkDescriptor->nextDescriptor = NULL;

	if(*tail == NULL)
	{
		/*first element in list*/
		*head = chunkDescriptor;
		*tail = chunkDescriptor;
		return;
	}

	(*tail)->nextDescriptor = chunkDescriptor;
	*tail = chunkDescriptor;
}

jng_bool jng_read(jng_handle handle)
{
	jng_bool			jdat12Chunks = JNG_FALSE;
	pchunk_descriptor	currentDescriptor;
	pchunk_descriptor	currentDescriptorRef;
	jng_bool			continueRead = JNG_TRUE;
	jng_bool			readResult = JNG_FALSE;
	jng_bool			storeChunk = JNG_FALSE;
	jng_bool			imageDataChunksEnded = JNG_FALSE;
	int					readError = JNG_OK;

	if(!handle)
		return JNG_FALSE;

	if(handle->readSucceeded)
		return JNG_TRUE;

	while(continueRead)
	{
		storeChunk = JNG_FALSE;
		currentDescriptor = ijng_read_next_chunk(handle);
		if(!currentDescriptor)
			break;

		switch(currentDescriptor->chunkPtr->header.chunkType.longVal)
		{
			case CHUNK_ID_IDAT:
				{
					if(imageDataChunksEnded || handle->chunkRefJhdr==NULL)
					{
						readError = JNG_ERR_CHUNKS_ORDER;
						break;
					}

					if(ijng_jhdr(handle)->alphaCompression != JNG_ALPHA_COMPRESSION_PNG)
					{
						readError = JNG_ERR_CHUNK_ALPHA_TYPE;
						break;
					}

					storeChunk = JNG_TRUE;

					currentDescriptorRef = ijng_create_chunk_ref(handle, currentDescriptor);
					if(!currentDescriptorRef)
					{
						readError = -1;
						break;
					}
					ijng_chunk_list_add(handle, currentDescriptorRef, &handle->chunksRefAlphaHead, &handle->chunksRefAlphaTail);

				}; break;
			case CHUNK_ID_IEND:
				{
					if(handle->flags & JNG_FLAG_STORE_ANCILLARY_CHUNKS)
					{
						storeChunk = JNG_TRUE;
					}
					if(handle->chunkRefJhdr && (handle->chunksRefJdat8Head || handle->chunksRefJdat12Head))
						readResult = JNG_TRUE;
					else
						readError = JNG_ERR_CHUNKS_COUNT;

					continueRead = JNG_FALSE;
				}; break;
			case CHUNK_ID_JDAA:
				{
					if(imageDataChunksEnded || handle->chunkRefJhdr==NULL)
					{
						readError = JNG_ERR_CHUNKS_ORDER;
						break;
					}

					if(ijng_jhdr(handle)->alphaCompression != JNG_ALPHA_COMPRESSION_JPEG)
					{
						readError = JNG_ERR_CHUNK_ALPHA_TYPE;
						break;
					}

					storeChunk = JNG_TRUE;

					currentDescriptorRef = ijng_create_chunk_ref(handle, currentDescriptor);
					if(!currentDescriptorRef)
					{
						readError = -1;
						break;
					}
					ijng_chunk_list_add(handle, currentDescriptorRef, &handle->chunksRefAlphaHead, &handle->chunksRefAlphaTail);
				}; break;
			case CHUNK_ID_JDAT:
				{
					if(imageDataChunksEnded || handle->chunkRefJhdr==NULL)
					{
						readError = JNG_ERR_CHUNKS_ORDER;
						break;
					}

					storeChunk = JNG_TRUE;

					currentDescriptorRef = ijng_create_chunk_ref(handle, currentDescriptor);
					if(!currentDescriptorRef)
					{
						readError = -1;
						break;
					}
					if(jdat12Chunks)
						ijng_chunk_list_add(handle, currentDescriptorRef, &handle->chunksRefJdat12Head, &handle->chunksRefJdat12Tail);
					else
						ijng_chunk_list_add(handle, currentDescriptorRef, &handle->chunksRefJdat8Head, &handle->chunksRefJdat8Tail);
				}; break;
			case CHUNK_ID_JHDR:
				{
					if(imageDataChunksEnded)
					{
						readError = JNG_ERR_CHUNKS_ORDER;
						break;
					}

					if(handle->chunkRefJhdr)
					{
						readError = JNG_ERR_CHUNKS_COUNT;
						break;
					}
					handle->chunkRefJhdr = ijng_create_chunk_ref(handle, currentDescriptor);
					storeChunk = JNG_TRUE;
				}; break;
			case CHUNK_ID_JSEP:
				{
					if(imageDataChunksEnded || handle->chunkRefJhdr==NULL)
					{
						readError = JNG_ERR_CHUNKS_ORDER;
						break;
					}

					if(handle->flags & JNG_FLAG_STORE_ANCILLARY_CHUNKS)
					{
						storeChunk = JNG_TRUE;
					}
					jdat12Chunks = JNG_TRUE;
				}; break;
			default:
				if(handle->flags & JNG_FLAG_STORE_ANCILLARY_CHUNKS)
				{
					storeChunk = JNG_TRUE;
				}
				imageDataChunksEnded = JNG_TRUE;
				break;
		}

		if(readError!=JNG_OK)
		{
			if(readError!=-1)
				JNG_TRACE_ERROR_CHUNK(handle, JNG_ERR_CHUNKS_ORDER, currentDescriptor->chunkPtr->header.chunkType.longVal, currentDescriptor->chunkSeq);
			ijng_free_chunk(handle, currentDescriptor);
			currentDescriptor = NULL;
			continueRead = JNG_FALSE;
		}

		if(currentDescriptor && storeChunk)
		{
			ijng_chunk_list_add(handle, currentDescriptor, &handle->chunksHead, &handle->chunksTail);
		}
		else
		{
			ijng_free_chunk(handle, currentDescriptor);
		}
	}

	if(handle->chunksRefJdat8Head)
	{
		handle->jpegSourceType = JNG_JPEG_SRC_8BITS;
	}
#ifdef JNG_JPEG_12_BITS
	else if(handle->chunksRefJdat12Head)
	{
		handle->jpegSourceType = JNG_JPEG_SRC_12BITS;
	}
#endif/*JNG_JPEG_12_BITS*/
	if(handle->jpegSourceType==JNG_JPEG_SRC_DEFAULT)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CHUNK_UNSUPPORTED_JPEG_FORMAT);
		return JNG_FALSE;
	}

	handle->readSucceeded = readResult;

	return readResult;
}

unsigned long jng_get_image_width(jng_handle handle)
{
	if(!handle)
		return 0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	return JNG_NBO2MBO_32U(ijng_jhdr(handle)->width);
}

unsigned long jng_get_image_height(jng_handle handle)
{
	if(!handle)
		return 0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	return JNG_NBO2MBO_32U(ijng_jhdr(handle)->height);
}

int jng_get_image_alpha_components(jng_handle handle)
{
	if(!handle)
		return (JNG_COLOR_COMPONENTS)0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return (JNG_COLOR_COMPONENTS)0;
	}

	if(handle->chunksRefAlphaHead)
		return 1;
	else
		return 0;
}

JNG_COLOR_COMPONENTS jng_get_image_components(jng_handle handle)
{
	if(!handle)
		return (JNG_COLOR_COMPONENTS)0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return (JNG_COLOR_COMPONENTS)0;
	}

	switch(ijng_jhdr(handle)->colorType)
	{
		case 8: return JNG_GREY;
		case 10: return JNG_COLOR;
		case 12: return JNG_GREY_ALPHA;
		case 14: return JNG_COLOR_ALPHA;
	}

	return (JNG_COLOR_COMPONENTS)0;
}

int jng_get_alpha_channel_bits(jng_handle handle)
{
	if(!handle)
		return 0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

#ifndef JNG_16BIT_SAMPLES
	if(ijng_jhdr(handle)->alphaSampleDepth>=16)
		return 8;
	else
		return ijng_jhdr(handle)->alphaSampleDepth;
#else
	return ijng_jhdr(handle)->alphaSampleDepth;
#endif
}

int jng_get_colors_channel_bits(jng_handle handle)
{
	if(!handle)
		return 0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	if(ijng_jhdr(handle)->imageSampleDepth != 8 && ijng_jhdr(handle)->imageSampleDepth != 12 && ijng_jhdr(handle)->imageSampleDepth != 20)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CORRUPTED_HANDLE_DATA);
		return 0;
	}

	return (int)(ijng_jhdr(handle)->imageSampleDepth);
}

jng_bool jng_set_out_alpha_channel_bits(jng_handle handle, JNG_OUT_BITS_PER_SAMPLE sampleDepth)
{
	if(!handle)
		return JNG_FALSE;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return JNG_FALSE;
	}

	if(handle->decodeStruct)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_ALREADY_STARTED);
		return JNG_FALSE;
	}

	if(sampleDepth!=JNG_OUT_BITS_DEFAULT && sampleDepth!=JNG_OUT_BITS_8 && sampleDepth!=JNG_OUT_BITS_16)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_INVALID_FUNCTION_ARGUMENT);
		return JNG_FALSE;
	}

	if(!handle->chunksRefAlphaHead)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_NO_ALPHA_CHANNEL);
		return JNG_FALSE;/*No alpha in image*/
	}

	handle->alphaOutSampleBitsDepth = sampleDepth;
	
	return JNG_TRUE;
}

jng_bool jng_set_out_color_channel_bits(jng_handle handle, JNG_OUT_BITS_PER_SAMPLE sampleDepth)
{
	if(!handle)
		return JNG_FALSE;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return JNG_FALSE;
	}

	if(handle->decodeStruct)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_ALREADY_STARTED);
		return JNG_FALSE;
	}

	if(sampleDepth!=JNG_OUT_BITS_DEFAULT && sampleDepth!=JNG_OUT_BITS_8 && sampleDepth!=JNG_OUT_BITS_16)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_INVALID_FUNCTION_ARGUMENT);
		return JNG_FALSE;
	}

	handle->colorOutSampleBitsDepth = sampleDepth;

	return JNG_TRUE;
}

int jng_get_out_alpha_channel_bits(jng_handle handle)
{
	if(!handle)
		return 0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	if(!handle->chunksRefAlphaHead)
		return 0;/*No alpha in image*/

	if(handle->alphaOutSampleBitsDepth!=JNG_OUT_BITS_DEFAULT)
		return handle->alphaOutSampleBitsDepth;
	else
		return ijng_jhdr(handle)->alphaSampleDepth;
}

int jng_get_out_color_channel_bits(jng_handle handle)
{
	if(!handle)
		return 0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	if(handle->colorOutSampleBitsDepth!=JNG_OUT_BITS_DEFAULT)
	{
		return handle->colorOutSampleBitsDepth;
	}
	else
	{
		switch(handle->jpegSourceType)
		{
		case JNG_JPEG_SRC_8BITS:
			return 8;
#ifdef JNG_JPEG_12_BITS
		case JNG_JPEG_SRC_12BITS:
			return 12;
#endif/*JNG_JPEG_12_BITS*/
		}

		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CHUNK_UNSUPPORTED_JPEG_FORMAT);
		return 0;
	}
}

int jng_get_out_alpha_channel_bytes(jng_handle handle)
{
	int bytesNum;
	
	if(!handle)
		return 0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	if(!handle->chunksRefAlphaHead)
		return 0;/*No alpha in image*/

	bytesNum = (handle->alphaOutSampleBitsDepth <= 8) ? 1 : (handle->alphaOutSampleBitsDepth >> 3);

	return  bytesNum;
}

int jng_get_out_color_channel_bytes(jng_handle handle)
{
	int bytesNum;

	if(!handle)
		return 0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	bytesNum = (handle->colorOutSampleBitsDepth <= 8) ? 1 : (handle->colorOutSampleBitsDepth >> 3);

	return  bytesNum;
}

jng_bool jng_set_color_jpeg_src_type(jng_handle handle, JNG_JPEG_SRC_TYPE jpgSrcType)
{
	if(!handle)
		return JNG_FALSE;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return JNG_FALSE;
	}

	if(handle->decodeStruct)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_ALREADY_STARTED);
		return JNG_FALSE;
	}

	if(jpgSrcType == JNG_JPEG_SRC_DEFAULT)
	{
		if(handle->chunksRefJdat8Head)
		{
			handle->jpegSourceType = JNG_JPEG_SRC_8BITS;
			return JNG_TRUE;
		}
#ifdef JNG_JPEG_12_BITS
		else if(handle->chunksRefJdat12Head)
		{
			handle->jpegSourceType = JNG_JPEG_SRC_12BITS;
			return JNG_TRUE;
		}
#endif/*JNG_JPEG_12_BITS*/
		else
		{
			JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_NO_SUCH_JDAT_CHUNK_TYPE);
		}
	}
	else if(jpgSrcType == JNG_JPEG_SRC_8BITS)
	{
		if(!handle->chunksRefJdat8Head)
		{
			JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_NO_SUCH_JDAT_CHUNK_TYPE);
			return JNG_FALSE;
		}

		handle->jpegSourceType = jpgSrcType;
		return JNG_TRUE;
	}
#ifdef JNG_JPEG_12_BITS
	else if(jpgSrcType == JNG_JPEG_SRC_12BITS)
	{
		if(!handle->chunksRefJdat12Head)
		{
			JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_NO_SUCH_JDAT_CHUNK_TYPE);
			return JNG_FALSE;
		}

		handle->jpegSourceType = jpgSrcType;
		return JNG_TRUE;
	}
#endif/*JNG_JPEG_12_BITS*/
	
	JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_INVALID_FUNCTION_ARGUMENT);
	return JNG_FALSE;
}

JNG_JPEG_SRC_TYPE jng_get_jpeg_src_type(jng_handle handle)
{
	if(!handle)
		return (JNG_JPEG_SRC_TYPE)0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return (JNG_JPEG_SRC_TYPE)0;
	}

	return handle->jpegSourceType;
}

jng_bool jng_set_out_color_space(jng_handle handle, JNG_COLOR_SPACE space)
{
	if(!handle)
		return JNG_FALSE;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return JNG_FALSE;
	}

	if(handle->decodeStruct)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_ALREADY_STARTED);
		return JNG_FALSE;
	}

	if(space!=JNG_GRAYSCALE && space!=JNG_YCbCr && space!=JNG_RGB && space!=JNG_CMYK && space!=JNG_YCCK)
	{
		JNG_TRACE_ERROR_ARG(handle);
		return JNG_FALSE;
	}

	handle->outColorSpace = space;
	return JNG_TRUE;
}

int jng_get_out_alpha_components_num(jng_handle handle)
{
	int colorComponentsCount = 0;

	if(!handle)
		return 0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	if(!handle->chunksRefAlphaHead)
		return 0;

	return 1;
}

int jng_get_out_color_components_num(jng_handle handle)
{
	int colorComponentsCount = 0;

	if(!handle)
		return 0;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	switch(handle->outColorSpace)
	{
	case JNG_GRAYSCALE: 
		colorComponentsCount = 1;
		break;
	case JNG_YCbCr:
	case JNG_RGB:
		colorComponentsCount = 3;
		break;
	case JNG_CMYK:
	case JNG_YCCK:
		colorComponentsCount = 4;
		break;
	default:
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CORRUPTED_HANDLE_DATA);
		return 0;
	}

	return colorComponentsCount;
}

int jng_get_out_pixel_components_num(jng_handle handle)
{
	int componentsCount = jng_get_out_color_components_num(handle);

	if(!handle)
		return 0;

	if(!componentsCount)
		return 0;

	if(handle->chunksRefAlphaHead)
		componentsCount++;

	return componentsCount;
}

jng_bool jng_set_alpha_before_color(jng_handle handle)
{
	if(!handle)
		return JNG_FALSE;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return JNG_FALSE;
	}

	if(handle->decodeStruct)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_ALREADY_STARTED);
		return JNG_FALSE;
	}

	handle->outAlphaBeforeColor = JNG_TRUE;

	return JNG_TRUE;
}

jng_bool jng_start_decode(jng_handle handle)
{
	pjng_decoder_interface	decoderAlpha = NULL;
	pjng_decoder_interface	decoderColors = NULL;
	pjng_decode_struct		decodeStruct = NULL;
	int						alphaChannelBytesNum = jng_get_out_alpha_channel_bytes(handle);
	int						colorChannelBytesNum = jng_get_out_color_channel_bytes(handle);
	jng_bool				jpegSrcFormatInvalid = JNG_FALSE;

	if(!handle)
		return JNG_FALSE;

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return JNG_FALSE;
	}

	if(handle->decodeStruct)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_ALREADY_STARTED);
		return JNG_FALSE;
	}

	if(colorChannelBytesNum <= 0)
	{
		return JNG_FALSE;
	}

	jpegSrcFormatInvalid = handle->jpegSourceType!=JNG_JPEG_SRC_8BITS;

#ifdef JNG_JPEG_12_BITS
	jpegSrcFormatInvalid &= handle->jpegSourceType!=JNG_JPEG_SRC_12BITS
#endif

	if(jpegSrcFormatInvalid)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CORRUPTED_HANDLE_DATA);
		return JNG_FALSE;
	}

	if(handle->jpegSourceType!=JNG_JPEG_SRC_8BITS && !handle->chunksRefJdat8Head)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CORRUPTED_HANDLE_DATA);
		return JNG_FALSE;
	}

#ifdef JNG_JPEG_12_BITS
	if(handle->jpegSourceType!=JNG_JPEG_SRC_12BITS && !handle->chunksRefJdat12Head)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CORRUPTED_HANDLE_DATA);
		return JNG_FALSE;
	}
#endif

	if(ijng_jhdr(handle)->alphaInterlace != 0)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_UNSUPPORTED_ALPHA_INTERLACE);
		return JNG_FALSE;
	}

	if(handle->chunksRefAlphaHead)
	{
		switch(ijng_jhdr(handle)->alphaCompression)
		{
			case JNG_ALPHA_COMPRESSION_PNG:
				decoderAlpha = ijng_decode_create_alpha_png(handle, handle->chunksRefAlphaHead, jng_get_out_alpha_channel_bytes(handle));
				break;
			case JNG_ALPHA_COMPRESSION_JPEG:
				decoderAlpha = ijng_decode_create_alpha_jpeg(handle, handle->chunksRefAlphaHead, jng_get_out_alpha_channel_bytes(handle));
				break;
			default:
				JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CORRUPTED_HANDLE_DATA);
				return JNG_FALSE;
		}

		if(!decoderAlpha)
			return JNG_FALSE;
	}
	else
	{
		decoderAlpha = NULL;
	}
	

	decoderColors = ijng_decode_create_color_jpeg(handle, handle->chunksRefJdat8Head, jng_get_out_color_channel_bytes(handle), handle->jpegSourceType);	

	if(!decoderColors)
	{
		decoderAlpha->finishProc(decoderAlpha);
		return JNG_FALSE;
	}

	decodeStruct = (pjng_decode_struct)ijng_alloc(handle, sizeof(jng_decode_struct));
	if(!decodeStruct)
	{
		decoderAlpha->finishProc(decoderAlpha);
		decoderColors->finishProc(decoderColors);

		JNG_TRACE_ERROR_MALLOC(handle);
		return JNG_FALSE;
	}

	memset(decodeStruct, 0, sizeof(jng_decode_struct));

	decodeStruct->alphaDecoder = decoderAlpha;
	decodeStruct->colorDecoder = decoderColors;
	if(decoderAlpha && decoderAlpha->bytesPerChannel > 0)
	{
		decodeStruct->alphaScanline = ijng_alloc(handle, decoderAlpha->bytesPerChannel * JNG_NBO2MBO_32U(ijng_jhdr(handle)->width));
		if(!decodeStruct->alphaScanline)
		{
			decoderAlpha->finishProc(decoderAlpha);
			decoderColors->finishProc(decoderColors);
			ijng_free(handle, decodeStruct);

			JNG_TRACE_ERROR_MALLOC(handle);
			return JNG_FALSE;
		}
	}

	decodeStruct->colorScanline = ijng_alloc(handle, decoderColors->bytesPerChannel * jng_get_out_color_components_num(handle) * JNG_NBO2MBO_32U(ijng_jhdr(handle)->width));
	if(!decodeStruct->colorScanline)
	{
		decoderAlpha->finishProc(decoderAlpha);
		decoderColors->finishProc(decoderColors);
		ijng_free(handle, decodeStruct->alphaScanline);
		ijng_free(handle, decodeStruct);

		JNG_TRACE_ERROR_MALLOC(handle);
		return JNG_FALSE;
	}

	handle->decodeStruct = decodeStruct;

	return JNG_TRUE;
}

jng_bool jng_read_scanline(jng_handle handle, void* row)
{
	jng_bool		readRes;
	size_t			imgWidth, imgHeight;
	size_t			i;
	unsigned char	*outAlphaPtr;
	unsigned char	*outColorPtr;
	unsigned char	*inAlphaPtr = NULL;
	unsigned char	*inColorPtr = NULL;
	JNG_PIXEL_PROCESS_MODE	alphaProcessMode;
	JNG_PIXEL_PROCESS_MODE	colorsProcessMode;
	int	colorComponentsNum = jng_get_out_color_components_num(handle);
	int	colorsFullBytesNum = jng_get_out_color_channel_bytes(handle) * colorComponentsNum;
	int	alphaFullBytesNum = jng_get_out_alpha_channel_bytes(handle);

	JNG_ASSERT_ALWAYS(handle);

	if(!row)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_INVALID_FUNCTION_ARGUMENT);
		return 0;
	}

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	if(!handle->decodeStruct)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_NOT_STARTED);
		return 0;
	}

	imgWidth = JNG_NBO2MBO_32U(ijng_jhdr(handle)->width);
	imgHeight = JNG_NBO2MBO_32U(ijng_jhdr(handle)->height);

	if(handle->decodeStruct->currentDecodeLine >= imgHeight)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_END_OF_IMAGE);
		return JNG_FALSE;
	}
	if(!handle->decodeStruct->alphaDecoder)
	{
		alphaProcessMode = JNG_PIXEL_SKIP;
	}
	else
	{
		if(handle->alphaOutSampleBitsDepth == JNG_OUT_BITS_DEFAULT|| handle->alphaOutSampleBitsDepth == ijng_jhdr(handle)->alphaSampleDepth)
		{
			alphaProcessMode = JNG_PIXEL_COPY;
		}
		else if(handle->alphaOutSampleBitsDepth > ijng_jhdr(handle)->alphaSampleDepth)
		{
			alphaProcessMode = JNG_PIXEL_UPSCALE;
		}
		else
		{
			alphaProcessMode = JNG_PIXEL_DOWNSCALE;
		}
	}

	if(handle->colorOutSampleBitsDepth == JNG_OUT_BITS_DEFAULT || handle->colorOutSampleBitsDepth==8 && handle->jpegSourceType==JNG_JPEG_SRC_8BITS)
	{
		colorsProcessMode = JNG_PIXEL_COPY;
	}
	else if(handle->colorOutSampleBitsDepth==16)
	{
		colorsProcessMode = JNG_PIXEL_UPSCALE;
	}
	else
	{
		colorsProcessMode = JNG_PIXEL_DOWNSCALE;
	}

	if(handle->decodeStruct->alphaDecoder)
	{
		readRes =  handle->decodeStruct->alphaDecoder->readLineProc(handle->decodeStruct->alphaDecoder, handle->decodeStruct->alphaScanline);
		if(!readRes)
		{
			return JNG_FALSE;
		}
	}

	readRes =  handle->decodeStruct->colorDecoder->readLineProc(handle->decodeStruct->colorDecoder, handle->decodeStruct->colorScanline);
	if(!readRes)
	{
		return JNG_FALSE;
	}

	inAlphaPtr = (unsigned char*)handle->decodeStruct->alphaScanline;
	inColorPtr = (unsigned char*)handle->decodeStruct->colorScanline;

	if(handle->outAlphaBeforeColor)
	{
		outAlphaPtr = (unsigned  char*)row;
		outColorPtr = outAlphaPtr + alphaFullBytesNum;
	}
	else
	{
		outColorPtr = (unsigned  char*)row;
		outAlphaPtr = outColorPtr + colorsFullBytesNum;
	}

	for(i = 0; i < imgWidth; i++)
	{
		switch(alphaProcessMode)
		{
		case JNG_PIXEL_COPY:
			{
				*outAlphaPtr++ = *inAlphaPtr++;
				if(ijng_jhdr(handle)->alphaSampleDepth == 16)
					*outAlphaPtr++ = *inAlphaPtr++;
			}
			break;
		case JNG_PIXEL_UPSCALE:
			{
				if(handle->alphaOutSampleBitsDepth == 8)
				{
					switch(ijng_jhdr(handle)->alphaSampleDepth)
					{
					case 1:
						*outAlphaPtr++ = (*inAlphaPtr++) * 0xff;
						break;
					case 2:
						*outAlphaPtr++ = (*inAlphaPtr++) * 0x55;
						break;
					case 4:
						*outAlphaPtr++ = (*inAlphaPtr++) * 0x11;
						break;
					}
				}
				else
				{
					switch(ijng_jhdr(handle)->alphaSampleDepth)
					{
					case 1:
						*((unsigned short*)outAlphaPtr) = *((unsigned short*)inAlphaPtr) * 0xffff;
						outAlphaPtr += 2; inAlphaPtr += 2;
						break;
					case 2:
						*((unsigned short*)outAlphaPtr) = *((unsigned short*)inAlphaPtr) * 0x5555;
						outAlphaPtr += 2; inAlphaPtr += 2;
						break;
					case 4:
						*((unsigned short*)outAlphaPtr) = *((unsigned short*)inAlphaPtr) * 0x1111;
						outAlphaPtr += 2; inAlphaPtr += 2;
						break;
					case 8:
						*((unsigned short*)outAlphaPtr) = *((unsigned short*)inAlphaPtr) * 0xff;
						outAlphaPtr += 2; inAlphaPtr += 2;
						break;
					}
				}
			}
			break;
		case JNG_PIXEL_DOWNSCALE:
			{
				/*assume that we always will always downscale only 16 bits to 8 bits*/
#ifdef JNG_BIG_ENDIAN
				*outAlphaPtr++ = *inAlphaPtr++;
#else
				*outAlphaPtr++ = *(++inAlphaPtr);
#endif
				inAlphaPtr++;

			}
			break;
		}
		outAlphaPtr += colorsFullBytesNum;

		switch(colorsProcessMode)
		{
		case JNG_PIXEL_COPY:
			{
				switch(colorsFullBytesNum)
				{
				case 8: *outColorPtr++ = *inColorPtr++;
				case 7: *outColorPtr++ = *inColorPtr++;
				case 6: *outColorPtr++ = *inColorPtr++;
				case 5: *outColorPtr++ = *inColorPtr++;
				case 4: *outColorPtr++ = *inColorPtr++;
				case 3: *outColorPtr++ = *inColorPtr++;
				case 2: *outColorPtr++ = *inColorPtr++;
				case 1: *outColorPtr++ = *inColorPtr++;
				}
			}
			break;
		case JNG_PIXEL_UPSCALE:
			{
				/*expand colors*/
				/*expand 8 or 12 bits to 16 bits*/
				if(handle->jpegSourceType == JNG_JPEG_SRC_8BITS)
				{
					/* 8 -> 16*/
					switch(colorComponentsNum)
					{
					case 4: *((unsigned short*)outColorPtr) = *inColorPtr * 0x101; outColorPtr += 2; inColorPtr++;
					case 3: *((unsigned short*)outColorPtr) = *inColorPtr * 0x101; outColorPtr += 2; inColorPtr++;
					case 2: *((unsigned short*)outColorPtr) = *inColorPtr * 0x101; outColorPtr += 2; inColorPtr++;
					case 1: *((unsigned short*)outColorPtr) = *inColorPtr * 0x101; outColorPtr += 2; inColorPtr++;
					}
				}
				else
				{
					/* 12 -> 16 (using fixed point 16.16 to get more accurate result)*/
					switch(colorComponentsNum)
					{
					case 4: *((unsigned short*)outColorPtr) = (unsigned short)((((unsigned long)*((unsigned short*)inColorPtr))  * 0x1000F0) >> 16); outColorPtr += 2; inColorPtr += 2;
					case 3: *((unsigned short*)outColorPtr) = (unsigned short)((((unsigned long)*((unsigned short*)inColorPtr))  * 0x1000F0) >> 16); outColorPtr += 2; inColorPtr += 2;
					case 2: *((unsigned short*)outColorPtr) = (unsigned short)((((unsigned long)*((unsigned short*)inColorPtr))  * 0x1000F0) >> 16); outColorPtr += 2; inColorPtr += 2;
					case 1: *((unsigned short*)outColorPtr) = (unsigned short)((((unsigned long)*((unsigned short*)inColorPtr))  * 0x1000F0) >> 16); outColorPtr += 2; inColorPtr += 2;
					}
				}
			}
			break;
		case JNG_PIXEL_DOWNSCALE:
			{
				/*assume that we always will downscale 12 bits stored in unsigned short to 8 bits*/
				switch(colorComponentsNum)
				{
				case 4: *outColorPtr++ = (unsigned char)((*((unsigned short*)inColorPtr)) >> 4); inColorPtr += 2;
				case 3: *outColorPtr++ = (unsigned char)((*((unsigned short*)inColorPtr)) >> 4); inColorPtr += 2;
				case 2: *outColorPtr++ = (unsigned char)((*((unsigned short*)inColorPtr)) >> 4); inColorPtr += 2;
				case 1: *outColorPtr++ = (unsigned char)((*((unsigned short*)inColorPtr)) >> 4); inColorPtr += 2;
				}
			}
			break;
		}
		outColorPtr += alphaFullBytesNum;
	}

	handle->decodeStruct->currentDecodeLine++;

	return JNG_TRUE;
}

size_t jng_read_scanlines(jng_handle handle, void* rows[], size_t scanlinesNum)
{
	size_t	numRowsRead = 0;

	JNG_ASSERT_ALWAYS(handle);
	
	if(!rows || scanlinesNum == 0)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_INVALID_FUNCTION_ARGUMENT);
		return 0;
	}

	if(!handle->readSucceeded)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_IMAGE_READ_STATE);
		return 0;
	}

	if(!handle->decodeStruct)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_NOT_STARTED);
		return 0;
	}

	while(scanlinesNum > 0)
	{
		if(!jng_read_scanline(handle, rows[numRowsRead]))
			break;
		numRowsRead++;
	}

	return numRowsRead;
}

void jng_finish_decode(jng_handle handle)
{
	if(!handle)
		return;

	if(!handle->decodeStruct)
		return;

	if(handle->decodeStruct->alphaDecoder)
		handle->decodeStruct->alphaDecoder->finishProc(handle->decodeStruct->alphaDecoder);

	if(handle->decodeStruct->colorDecoder)
		handle->decodeStruct->colorDecoder->finishProc(handle->decodeStruct->colorDecoder);
	else
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CORRUPTED_HANDLE_DATA);

	if(handle->decodeStruct->alphaScanline)
		ijng_free(handle, handle->decodeStruct->alphaScanline);

	if(handle->decodeStruct->colorScanline)
		ijng_free(handle, handle->decodeStruct->colorScanline);
	else
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CORRUPTED_HANDLE_DATA);

	ijng_free(handle, handle->decodeStruct);
	handle->decodeStruct = NULL;
}

/********************************************************************/
/* JPEG decoding stuff (colors and alpha)*/

void jng_jpeg_init_source(j_decompress_ptr cinfo){}

boolean jng_jpeg_fill_input_buffer(j_decompress_ptr cinfo)
{
	pjng_decoder_interface src = (pjng_decoder_interface)cinfo->src;
	
	if(src->chunkDataDesciptor->nextDescriptor==NULL)
	{
		cinfo->src->next_input_byte = (JOCTET const *)"\xff\xd9";	/* fake EOI marker */
		cinfo->src->bytes_in_buffer = 2;
	}
	else
	{
		src->chunkDataDesciptor = (pchunk_descriptor)src->chunkDataDesciptor->nextDescriptor;
		cinfo->src->next_input_byte = src->chunkDataDesciptor->chunkPtr->data;
		cinfo->src->bytes_in_buffer = JNG_NBO2MBO_32U(src->chunkDataDesciptor->chunkPtr->header.dataLength);
	}

	return 1;
}

void jng_jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
	struct jpeg_source_mgr * src = cinfo->src;
  
	if (num_bytes > 0)
	{
		while (num_bytes > (long) src->bytes_in_buffer)
		{
			num_bytes -= (long) src->bytes_in_buffer;
			(void) (*src->fill_input_buffer) (cinfo);
		}
		src->next_input_byte += (size_t) num_bytes;
		src->bytes_in_buffer -= (size_t) num_bytes;
	}
}

void jng_jpeg_term_source(j_decompress_ptr cinfo) {}

void jng_jpeg_error_exit(j_common_ptr cinfo) {}

void jng_jpeg_emit_message(j_common_ptr cinfo, int msg_level){}

void jng_jpeg_reset_error_mgr(j_common_ptr cinfo)
{
	cinfo->err->num_warnings = 0;
	cinfo->err->msg_code = 0;
}

pjng_decoder_interface ijng_decode_create_color_jpeg(jng_handle handle, pchunk_descriptor chunksRefHead, int outBytesPerChannel, JNG_JPEG_SRC_TYPE bitsPerChannel)
{
	pjng_decoder_jepg	decoder = NULL;

	JNG_ASSERT_ALWAYS(handle);
	JNG_ASSERT_ALWAYS(chunksRefHead);

	if(bitsPerChannel!=JNG_JPEG_SRC_8BITS)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CHUNK_UNSUPPORTED_JPEG_FORMAT);
		return NULL;
	}

	decoder = (pjng_decoder_jepg)ijng_alloc(handle, sizeof(jng_decoder_jepg));
	if(!decoder)
	{
		JNG_TRACE_ERROR_MALLOC(handle);
		return NULL;
	}
	decoder->decoderCommon.handle = handle;
	decoder->decoderCommon.chunkDataDesciptor = chunksRefHead;
	decoder->decoderCommon.dataOffsetInDescriptor = 0;
	decoder->decoderCommon.readLineProc = ijng_decode_read_scanline_jpeg;
	decoder->decoderCommon.finishProc = ijng_decode_finish_jpeg;
	decoder->decoderCommon.bytesPerChannel = sizeof(JSAMPLE);

	decoder->cinfo.err=&decoder->jerr;
	decoder->jerr.error_exit = jng_jpeg_error_exit;
	decoder->jerr.emit_message = jng_jpeg_emit_message;
	decoder->jerr.reset_error_mgr = jng_jpeg_reset_error_mgr;

	jpeg_create_decompress(&decoder->cinfo);

	decoder->cinfo.src = (struct jpeg_source_mgr*)&decoder->src;

	decoder->src.pub.init_source = jng_jpeg_init_source;
	decoder->src.pub.fill_input_buffer = jng_jpeg_fill_input_buffer;
	decoder->src.pub.skip_input_data = jng_jpeg_skip_input_data;
	decoder->src.pub.resync_to_restart = jpeg_resync_to_restart; /* default method */
	decoder->src.pub.term_source = jng_jpeg_term_source;
	decoder->src.pub.bytes_in_buffer = JNG_NBO2MBO_32U(chunksRefHead->chunkPtr->header.dataLength);
	decoder->src.pub.next_input_byte = (JOCTET const *)chunksRefHead->chunkPtr->data;

	if(jpeg_read_header(&decoder->cinfo, 1)!=JPEG_HEADER_OK)
	{
		jpeg_finish_decompress(&decoder->cinfo);
		jpeg_destroy_decompress(&decoder->cinfo);
		ijng_free(handle, decoder);

		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_JPEG_COLORS);
		return NULL;
	}

	switch(handle->outColorSpace)
	{
	case JNG_GRAYSCALE:
		decoder->cinfo.out_color_space = JCS_GRAYSCALE;
		decoder->cinfo.num_components = 1;
		break;
	case JNG_YCbCr:
		decoder->cinfo.out_color_space = JCS_YCbCr;
		decoder->cinfo.num_components = 3;
		break;
	case JNG_RGB:
		decoder->cinfo.out_color_space = JCS_RGB;
		decoder->cinfo.num_components = 3;
		break;
	case JNG_CMYK:
		decoder->cinfo.out_color_space = JCS_CMYK;
		decoder->cinfo.num_components = 4;
		break;
	case JNG_YCCK:
		decoder->cinfo.out_color_space = JCS_YCCK;
		decoder->cinfo.num_components = 4;
		break;
	default:
		{
			jpeg_finish_decompress(&decoder->cinfo);
			jpeg_destroy_decompress(&decoder->cinfo);
			ijng_free(handle, decoder);

			JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_CORRUPTED_HANDLE_DATA);
			return NULL;
		}
	}

	decoder->cinfo.scale_num = 1;
	decoder->cinfo.scale_denom = 1;
	
	decoder->cinfo.output_gamma = handle->outputGamma;

	if(JNG_NBO2MBO_32U(ijng_jhdr(handle)->width)!=decoder->cinfo.image_width || JNG_NBO2MBO_32U(ijng_jhdr(handle)->height)!=decoder->cinfo.image_height)
	{
		jpeg_finish_decompress(&decoder->cinfo);
		jpeg_destroy_decompress(&decoder->cinfo);
		ijng_free(handle, decoder);

		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_COLOR_DIMENTIONS_MISMATCH);
		return NULL;
	}
	
	if(!jpeg_start_decompress(&decoder->cinfo))
	{
		jpeg_finish_decompress(&decoder->cinfo);
		jpeg_destroy_decompress(&decoder->cinfo);
		ijng_free(handle, decoder);

		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_JPEG_DECOMPRESSION);
		return NULL;
	}

	return (pjng_decoder_interface)decoder;
}

jng_bool ijng_decode_read_scanline_jpeg(pjng_decoder_interface decoder, void* buffer)
{
	JNG_ASSERT_ALWAYS(decoder);
	JNG_ASSERT_ALWAYS(buffer);

	((pjng_decoder_jepg)decoder)->scanlines[0] = (JSAMPROW)buffer;

	return jpeg_read_scanlines(&((pjng_decoder_jepg)decoder)->cinfo, ((pjng_decoder_jepg)decoder)->scanlines, 1) == 1;
}

jng_bool ijng_decode_finish_jpeg(pjng_decoder_interface decoder)
{
	pjng_decoder_jepg decoderJpeg = (pjng_decoder_jepg)decoder;

	JNG_ASSERT_ALWAYS(decoder);

	jpeg_finish_decompress(&decoderJpeg->cinfo);
	jpeg_destroy_decompress(&decoderJpeg->cinfo);

	ijng_free(decoderJpeg->decoderCommon.handle, decoderJpeg);

	return JNG_TRUE;
}

pjng_decoder_interface ijng_decode_create_alpha_jpeg(jng_handle handle, pchunk_descriptor chunksRefHead, int outBytesPerChannel)
{
	pjng_decoder_jepg	decoder = NULL;

	JNG_ASSERT_ALWAYS(handle);
	JNG_ASSERT_ALWAYS(chunksRefHead);

	decoder = (pjng_decoder_jepg)ijng_alloc(handle, sizeof(jng_decoder_jepg));
	if(!decoder)
	{
		JNG_TRACE_ERROR_MALLOC(handle);
		return NULL;
	}
	decoder->decoderCommon.handle = handle;
	decoder->decoderCommon.chunkDataDesciptor = chunksRefHead;
	decoder->decoderCommon.dataOffsetInDescriptor = 0;
	decoder->decoderCommon.readLineProc = ijng_decode_read_scanline_jpeg;
	decoder->decoderCommon.finishProc = ijng_decode_finish_jpeg;
	decoder->decoderCommon.bytesPerChannel = sizeof(JSAMPLE);

	decoder->cinfo.err=&decoder->jerr;
	decoder->jerr.error_exit = jng_jpeg_error_exit;
	decoder->jerr.emit_message = jng_jpeg_emit_message;
	decoder->jerr.reset_error_mgr = jng_jpeg_reset_error_mgr;

	jpeg_create_decompress(&decoder->cinfo);

	decoder->cinfo.src = (struct jpeg_source_mgr*)&decoder->src;

	decoder->src.pub.init_source = jng_jpeg_init_source;
	decoder->src.pub.fill_input_buffer = jng_jpeg_fill_input_buffer;
	decoder->src.pub.skip_input_data = jng_jpeg_skip_input_data;
	decoder->src.pub.resync_to_restart = jpeg_resync_to_restart; /* default method */
	decoder->src.pub.term_source = jng_jpeg_term_source;
	decoder->src.pub.bytes_in_buffer = JNG_NBO2MBO_32U(chunksRefHead->chunkPtr->header.dataLength);
	decoder->src.pub.next_input_byte = (JOCTET const *)chunksRefHead->chunkPtr->data;

	if(jpeg_read_header(&decoder->cinfo, 1)!=JPEG_HEADER_OK)
	{
		jpeg_finish_decompress(&decoder->cinfo);
		jpeg_destroy_decompress(&decoder->cinfo);
		ijng_free(handle, decoder);

		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_DECODE_JPEG_COLORS);
		return NULL;
	}

	decoder->cinfo.out_color_space = JCS_GRAYSCALE;
	decoder->cinfo.num_components = 1;
	decoder->cinfo.scale_num = 1;
	decoder->cinfo.scale_denom = 1;
	decoder->cinfo.output_gamma = 1;

	if(JNG_NBO2MBO_32U(ijng_jhdr(handle)->width)!=decoder->cinfo.image_width || JNG_NBO2MBO_32U(ijng_jhdr(handle)->height)!=decoder->cinfo.image_height)
	{
		jpeg_finish_decompress(&decoder->cinfo);
		jpeg_destroy_decompress(&decoder->cinfo);
		ijng_free(handle, decoder);

		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_ALPHA_DIMENTIONS_MISMATCH);
		return NULL;
	}

	if(!jpeg_start_decompress(&decoder->cinfo))
	{
		jpeg_finish_decompress(&decoder->cinfo);
		jpeg_destroy_decompress(&decoder->cinfo);
		ijng_free(handle, decoder);

		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_JPEG_DECOMPRESSION);
		return NULL;
	}

	return (pjng_decoder_interface)decoder;
}


/********************************************************************/
/* PNG decoding stuff*/

voidpf jng_zlib_alloc(voidpf opaque, uInt items, uInt size)
{
	void* allocRes = NULL;
	size_t allocSize = items * size;

	if(!opaque)
		return NULL;

	
	allocRes = ijng_alloc(((jng_handle)opaque), allocSize);
	if(allocRes)
	{
		memset(allocRes, 0, allocSize);
	}
	else
	{
		JNG_TRACE_ERROR_MALLOC((jng_handle)opaque);
	}

	return allocRes;
}

void jng_zlib_free(voidpf opaque, voidpf address)
{
	if(!opaque)
		return;

	ijng_free(((jng_handle)opaque), address);
}

pjng_decoder_interface ijng_decode_create_alpha_png(jng_handle handle, pchunk_descriptor chunksRefHead, int outBytesPerChannel)
{
	pjng_decoder_png decoder = NULL;
	int maxPixelDepth;

	JNG_ASSERT_ALWAYS(handle);
	JNG_ASSERT_ALWAYS(chunksRefHead);

	decoder = (pjng_decoder_png)ijng_alloc(handle, sizeof(jng_decoder_png));
	if(!decoder)
	{
		JNG_TRACE_ERROR_MALLOC(handle);
		return NULL;
	}

	memset(decoder, 0, sizeof(jng_decoder_png));

	decoder->decoderCommon.handle = handle;
	decoder->decoderCommon.chunkDataDesciptor = chunksRefHead;
	decoder->decoderCommon.dataOffsetInDescriptor = 0;
	decoder->decoderCommon.readLineProc = ijng_decode_read_scanline_png;
	decoder->decoderCommon.finishProc = ijng_decode_finish_png;
	decoder->decoderCommon.bytesPerChannel = ijng_jhdr(handle)->alphaSampleDepth <= 8 ? 1 : 2;

	maxPixelDepth = ijng_jhdr(handle)->alphaSampleDepth;

	/*
	if(ijng_jhdr(handle)->alphaSampleDepth < 8 && handle->alphaOutSampleBitsDepth > 0)
		max_pixel_depth = handle->alphaOutSampleBitsDepth;
	*/
	decoder->rowPixelBytesNum = PNG_ROWBYTES(maxPixelDepth, JNG_NBO2MBO_32U(ijng_jhdr(handle)->width));
	decoder->rowBufferBytesNum = decoder->rowPixelBytesNum + 1;/*add one byte for filter type marker*/

	decoder->rowBytesCur = (unsigned char*)ijng_alloc(handle, decoder->rowBufferBytesNum);
	decoder->rowBytesPrev = (unsigned char*)ijng_alloc(handle, decoder->rowBufferBytesNum);

	memset(decoder->rowBytesCur, 0, decoder->rowBufferBytesNum);
	memset(decoder->rowBytesPrev, 0, decoder->rowBufferBytesNum);

	if(!decoder->rowBytesCur || !decoder->rowBytesPrev)
	{
		if(decoder->rowBytesCur)
			ijng_free(handle, decoder->rowBytesCur);
		if(decoder->rowBytesPrev)
			ijng_free(handle, decoder->rowBytesPrev);
		ijng_free(handle, decoder);
		JNG_TRACE_ERROR_MALLOC(handle);
		return NULL;
	}

	decoder->zStream.zalloc = jng_zlib_alloc;
	decoder->zStream.zfree = jng_zlib_free;
	decoder->zStream.opaque = (voidpf)handle;

	if(inflateInit(&decoder->zStream) != Z_OK)
	{
		JNG_TRACE_ERROR_COMMON(handle, JNG_ERR_ZLIB_INFLATE_INIT);
		ijng_free(handle, decoder->rowBytesCur);
		ijng_free(handle, decoder->rowBytesPrev);
		ijng_free(handle, decoder);
		return NULL;
	}

	decoder->zStream.next_out = NULL;
	decoder->zStream.avail_out = 0;
	
	decoder->zStream.next_in = chunksRefHead->chunkPtr->data;
	decoder->zStream.avail_in =JNG_NBO2MBO_32U(chunksRefHead->chunkPtr->header.dataLength);

	return (pjng_decoder_interface)decoder;
}

jng_bool ijng_decode_read_scanline_png(pjng_decoder_interface decoder, void* buffer)
{
	pjng_decoder_png	decoderPng = ((pjng_decoder_png)decoder);
	int					ret;
	unsigned char*		rowOutBytesTmp;
	unsigned char*		rowInBytesTmp;
	unsigned short		*usRowOutPtr, *usRowInPtr;
	int					widthInPixels, i;
	int					shiftVal;
	unsigned char		bitsMask;

	JNG_ASSERT_ALWAYS(decoder);
	JNG_ASSERT_ALWAYS(buffer);

	/*setup zlib input parameters*/
	decoderPng->zStream.next_out = decoderPng->rowBytesCur;
	decoderPng->zStream.avail_out = decoderPng->rowBufferBytesNum;

	do
	{
		/*inflate compressed data until we have space in output buffer (size of output buffer is enough to hold one uncompressed row with filter type byte)*/
		if(!decoderPng->zStream.avail_in)
		{
			decoderPng->decoderCommon.chunkDataDesciptor = (pchunk_descriptor)decoderPng->decoderCommon.chunkDataDesciptor->nextDescriptor;
			if(!decoderPng->decoderCommon.chunkDataDesciptor)
			{
				JNG_TRACE_ERROR_COMMON(decoderPng->decoderCommon.handle, JNG_ERR_END_OF_DATA);
				return JNG_FALSE;
			}

			decoderPng->zStream.next_in = decoderPng->decoderCommon.chunkDataDesciptor->chunkPtr->data;
			decoderPng->zStream.avail_in = JNG_NBO2MBO_32U(decoderPng->decoderCommon.chunkDataDesciptor->chunkPtr->header.dataLength);
		}

		ret = inflate(&decoderPng->zStream, Z_PARTIAL_FLUSH);

		switch(ret)
		{
		case Z_STREAM_END:
			if(decoderPng->zStream.avail_in || decoderPng->zStream.avail_out || decoderPng->decoderCommon.chunkDataDesciptor->nextDescriptor)
			{
				JNG_TRACE_ERROR_COMMON(decoderPng->decoderCommon.handle, JNG_ERR_ZLIB_INFLATE_END_OF_STREAM);
			}
			break;
		case Z_OK:
			break;
		default:
			JNG_TRACE_ERROR_COMMON(decoderPng->decoderCommon.handle, JNG_ERR_ZLIB_INFLATE);
			return JNG_FALSE;
			break;
		}
	} while(decoderPng->zStream.avail_out);

	if(decoderPng->rowBytesCur[0])
	{
		/*filter inflated data*/
		if(!ijng_decode_read_filter_row_png(decoder, decoderPng->rowBufferBytesNum, decoderPng->rowBytesCur[0], decoderPng->rowBytesCur + 1, decoderPng->rowBytesPrev + 1))
			return JNG_FALSE;
	}

	/*copy decoded data in output buffer*/
	if(ijng_jhdr(decoderPng->decoderCommon.handle)->alphaSampleDepth == 8)
	{
		memcpy(buffer, decoderPng->rowBytesCur + 1, decoderPng->rowBufferBytesNum - 1);
	}
	else if(ijng_jhdr(decoderPng->decoderCommon.handle)->alphaSampleDepth == 16)
	{
		widthInPixels = (decoderPng->rowBufferBytesNum - 1) >> 1;
		usRowOutPtr = (unsigned short*)buffer;
		usRowInPtr = (unsigned short*)(decoderPng->rowBytesCur + 1);
		for(i = 0; i < widthInPixels; i++)
		{
			*usRowOutPtr = JNG_NBO2MBO_16U(*usRowInPtr);
			usRowOutPtr++;
			usRowInPtr++;
		}
	}
	else if(ijng_jhdr(decoderPng->decoderCommon.handle)->alphaSampleDepth < 8)
	{
		/*expand each pixel in separate byte without changing "bits per sample" value*/
		/*
			Pixels smaller than a byte never cross byte boundaries; they are packed into bytes with the leftmost pixel in the high-order bits of a byte, the RIGHTMOST in the LOW-ORDER BITS.
		*/
		rowOutBytesTmp = (unsigned char*)buffer;
		rowInBytesTmp = decoderPng->rowBytesCur + 1;

		widthInPixels = JNG_NBO2MBO_32U(ijng_jhdr(decoderPng->decoderCommon.handle)->width);
		bitsMask = ((unsigned char)0xff) >> (8 - ijng_jhdr(decoderPng->decoderCommon.handle)->alphaSampleDepth);
		shiftVal = 8 - ijng_jhdr(decoderPng->decoderCommon.handle)->alphaSampleDepth;

		for(i = 0; i < widthInPixels; i++)
		{
			*rowOutBytesTmp = ((*rowInBytesTmp) >> shiftVal) & bitsMask;
			rowOutBytesTmp++;
			shiftVal -= ijng_jhdr(decoderPng->decoderCommon.handle)->alphaSampleDepth;
			if(shiftVal < 0)
			{
				shiftVal = 8 - ijng_jhdr(decoderPng->decoderCommon.handle)->alphaSampleDepth;
				rowInBytesTmp++;
			}
		}
	}
	else
	{
		JNG_TRACE_ERROR_COMMON(decoderPng->decoderCommon.handle, JNG_ERR_CORRUPTED_HANDLE_DATA);
		return JNG_FALSE;
	}

	/*swap pointers to current and previous lines*/
	rowInBytesTmp = decoderPng->rowBytesPrev;
	decoderPng->rowBytesPrev = decoderPng->rowBytesCur;
	decoderPng->rowBytesCur = rowInBytesTmp;
	
	return JNG_TRUE;
}

jng_bool ijng_decode_finish_png(pjng_decoder_interface decoder)
{
	pjng_decoder_png decoderPng = (pjng_decoder_png)decoder;

	JNG_ASSERT_ALWAYS(decoder);

	inflateEnd(&decoderPng->zStream);

	ijng_free(decoderPng->decoderCommon.handle, decoderPng->rowBytesCur);
	ijng_free(decoderPng->decoderCommon.handle, decoderPng->rowBytesPrev);
	ijng_free(decoderPng->decoderCommon.handle, decoder);

	return JNG_TRUE;
}

jng_bool ijng_decode_read_filter_row_png(pjng_decoder_interface decoder, size_t bytesNumInRow, int filterType, unsigned char* currentRow, unsigned char* prevRow)
{
	pjng_decoder_png	decoderPng = ((pjng_decoder_png)decoder);

	JNG_ASSERT_ALWAYS(decoder);
	JNG_ASSERT_ALWAYS(currentRow);
	JNG_ASSERT_ALWAYS(prevRow);

	switch(filterType)
	{
      case PNG_FILTER_VALUE_NONE:
         break;
      case PNG_FILTER_VALUE_SUB:
		  {
			 unsigned long i;
			 size_t istop = bytesNumInRow;
			 unsigned long bpp = (ijng_jhdr(decoderPng->decoderCommon.handle)->alphaSampleDepth + 7) >> 3;
			 unsigned char* rp = currentRow + bpp;
			 unsigned char* lp = currentRow;

			 for(i = bpp; i < istop; i++)
			 {
				*rp = (unsigned char)(((int)(*rp) + (int)(*lp++)) & 0xff);
				rp++;
			 }
		  }
		  break;
      case PNG_FILTER_VALUE_UP:
		  {
			 unsigned long i;
			 unsigned long istop = bytesNumInRow;
			 unsigned char* rp = currentRow;
			 unsigned char* pp = prevRow;

			 for(i = 0; i < istop; i++)
			 {
				*rp = (unsigned char)(((int)(*rp) + (int)(*pp++)) & 0xff);
				rp++;
			 }
		  }
		  break;
      case PNG_FILTER_VALUE_AVG:
		  {
			 unsigned long i;
			 unsigned char* rp = currentRow;
			 unsigned char* pp = prevRow;
			 unsigned char* lp = currentRow;
			 unsigned long bpp = (ijng_jhdr(decoderPng->decoderCommon.handle)->alphaSampleDepth + 7) >> 3;
			 unsigned long istop = bytesNumInRow - bpp;

			 for(i = 0; i < bpp; i++)
			 {
				*rp = (unsigned char)(((int)(*rp) + ((int)(*pp++) / 2 )) & 0xff);
				rp++;
			 }

			 for(i = 0; i < istop; i++)
			 {
				*rp = (unsigned char)(((int)(*rp) + (int)(*pp++ + *lp++) / 2 ) & 0xff);
				rp++;
			 }
		  }
		  break;
      case PNG_FILTER_VALUE_PAETH:
		  {
			 unsigned long i;
			 unsigned char* rp = currentRow;
			 unsigned char* pp = prevRow;
			 unsigned char* lp = currentRow;
			 unsigned char* cp = prevRow;
			 unsigned long bpp = (ijng_jhdr(decoderPng->decoderCommon.handle)->alphaSampleDepth + 7) >> 3;
			 unsigned long istop = bytesNumInRow - bpp;

			 for(i = 0; i < bpp; i++)
			 {
				*rp = (unsigned char)(((int)(*rp) + (int)(*pp++)) & 0xff);
				rp++;
			 }

			 for(i = 0; i < istop; i++)   /// use leftover rp,pp
			 {
				int a, b, c, pa, pb, pc, p;

				a = *lp++;
				b = *pp++;
				c = *cp++;

				p = b - c;
				pc = a - c;

				pa = p < 0 ? -p : p;
				pb = pc < 0 ? -pc : pc;
				pc = (p + pc) < 0 ? -(p + pc) : p + pc;

				p = (pa <= pb && pa <=pc) ? a : (pb <= pc) ? b : c;

				*rp = (unsigned char)(((int)(*rp) + p) & 0xff);
				rp++;
			 }
		  }
		  break;
      default:
		  {
			  JNG_TRACE_ERROR_COMMON(decoderPng->decoderCommon.handle, JNG_ERR_CORRUPTED_HANDLE_DATA);
			  return JNG_FALSE;
		  }
	}

	return JNG_TRUE;
}